package aduros.game {

import com.whirled.avrg.*;

import aduros.util.F;

/**
 * FIXME: This doesn't work for guest players. Whirled bug? See Scribble for another implementation.
 *
 * A basic population balancer, useful for embedding AVRGs. When a player joins the AVRG, his first
 * room joined is examined. If it's a "lobby", based on your isLobby predicate, a parlor is selected
 * and the player is moved there. This only happens once per session, per player.
 */
public class LoadBalancer
{
    /**
     * @param max "Full" capacity of a parlor. LoadBalancer will refuse to send a player to a full
     *     parlor unless all parlors are full.
     * @param isLobby Predicate function taking a roomId (int), returning Boolean whether or not
     *     it is a "lobby".
     * @param parlorIds A list of "parlor" roomIds.
     */
    public function LoadBalancer (ctrl :AVRServerGameControl, max :int, isLobby :Function, parlorIds :Array)
    {
        _ctrl = ctrl;
        _max = max;
        _isLobby = isLobby;
        _parlorIds = parlorIds;

        _ctrl.game.addEventListener(AVRGameControlEvent.PLAYER_JOINED_GAME, onPlayerJoin);
    }

    protected function onPlayerJoin (event :AVRGameControlEvent) :void
    {
        var player :PlayerSubControlServer = _ctrl.getPlayer(int(event.value));

        player.addEventListener(AVRGamePlayerEvent.ENTERED_ROOM, onRoomEntered);
    }

    protected function onRoomEntered (event :AVRGamePlayerEvent) :void
    {
        var player :PlayerSubControlServer = _ctrl.getPlayer(event.playerId);
        var roomId :int = int(event.value);

        // Only fire once per session
        player.removeEventListener(AVRGamePlayerEvent.ENTERED_ROOM, onRoomEntered);

        if (_isLobby(roomId)) {
            var bestRoomId :int = getBestRoom();
            if (roomId != bestRoomId) {
                player.moveToRoom(bestRoomId);
            }
        }
    }

    protected function getPopulation (roomId :int) :int
    {
        try {
            // Would be nice if this didn't spam the hell out of the logs
            return _ctrl.getRoom(roomId).getPlayerIds().length;
        } catch (_ :*) { }

        return 0;
    }

    public function getBestRoom () :int
    {
        // TODO: Should probably cache this somewhere
        var pop :Array = []; // roomId -> population
        for each (var parlorId :int in _parlorIds) {
            pop[parlorId] = getPopulation(parlorId);
        }

        // List of parlor roomIds that are below max capacity
        var belowCapacity :Array = _parlorIds.filter(function (parlorId :int, ..._) :Boolean {
            return pop[parlorId] < _max;
        });

        if (belowCapacity.length > 0) {
            // The highest populated room that's below capacity
            return F.foldl(function (bestId :int, parlorId :int) :int {
                return (pop[parlorId] > pop[bestId]) ? parlorId : bestId;
            }, belowCapacity[0], belowCapacity);

        } else {
            // The lowest populated room, if all the rooms are full anyways
            return F.foldl(function (bestId :int, parlorId :int) :int {
                return (pop[parlorId] < pop[bestId]) ? parlorId : bestId;
            }, _parlorIds[0], _parlorIds);
        }
    }

    protected var _ctrl :AVRServerGameControl;

    protected var _max :int;
    protected var _isLobby :Function; // function (int) :Boolean
    protected var _parlorIds :Array; // of int
}

}
